package iosu.java.games.razer;

import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARFINISHED_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARUPDATE_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CREATEGAME_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMEREADY_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.HELLO_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.NOGAMETOJOIN_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.NOOP_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.PLAYERADDED_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMENOTREADY_RESPONSE;
import iosu.java.games.razer.api.CarInfo;
import iosu.java.games.razer.api.Game;
import iosu.java.games.razer.api.protocol.RazerProtocolBinarySocket;
import iosu.java.games.razer.api.protocol.RazerProtocolMessage;
import iosu.java.games.razer.characters.Car;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.management.InvalidApplicationException;
import javax.swing.GrayFilter;
import javax.swing.JOptionPane;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** 
 * esta clase implementa el cliente de juego para Razer
 * Sus responsabilidades son:
 * - cargar los recursos del juego
 * - lanzar el hilo de networking
 * - implementa el bucle enterno de actualizacion del mundo
 * @author iosu
 * 
 */
public class RazerClient extends Applet implements Runnable, KeyListener {

	private static final long serialVersionUID = -6144620762322911819L;


	// aqui van toooodos los sprites
	// luego los cargo en el init
	private Image image, character, enemy1img, enemy2img, enemy3img;
	private Graphics second;
	// tengo 4 coches
	protected Car car;
	private Car enemy1;
	private Car enemy2;
	private Car enemy3;
	// este es el que manejo
	private Car jugador;
	private InetAddress serverIP;
	private int serverPORT;
	protected String _serverIP = "127.0.0.1"; 
	protected int _serverPORT = 9911;
	protected UUID idcliente = UUID.randomUUID();

    protected ClientStatus status = ClientStatus.IDLE;
	private int serverid;
	private int carnum=0;
	private Font normalfont;
	private Font bigfont;
	protected BufferedImage track, trackart, splash;
	protected Track track1, currenttrack;
	protected String playername;
	
	private static Logger logger;
	
	private static int countdownindex=0;
	private int lapnumber=10;
	
	private int[] tmpresults;
	private String[] players = new String[4];


    protected BlockingQueue<RazerProtocolMessage> sendQueue;
    protected BlockingQueue<RazerProtocolMessage> readQueue;
    
    private Thread networkingThread;
    
    private int FPS;
    private int numberofplayers;
    
    private int accframes=0;
    
    
    // partida en curso
    private Game game; 
	
	static{
		logger= LoggerFactory.getLogger(RazerClient.class);
	}
	
	   public UUID getIdcliente() {
	        return idcliente;
	    }

	    public void setIdcliente(UUID idcliente) {
	        this.idcliente = idcliente;
	    }


	@Override
	public void init() {
		this.status = ClientStatus.INITIALIZING;
		setSize(800, 480);
		setBackground(Color.BLACK);
		setFocusable(true);
		Frame frame = (Frame) this.getParent().getParent();
		if(frame!=null) frame.setTitle("RaZer!");
		sendQueue=new ArrayBlockingQueue<RazerProtocolMessage>(1024);
		readQueue=new ArrayBlockingQueue<RazerProtocolMessage>(1024);
		loadGameResources();
	}
	
	/**
	 * este metodo se encarga de cargar todos los recursos del juego 
	 */
	protected void loadGameResources(){
		try {
		    logger.debug("cargando recursos...");
			// para cargar los recursos del applet

			Font f = Font.createFont( Font.TRUETYPE_FONT,  this.getClass().getClassLoader().getResourceAsStream("BADABB.ttf") );
			normalfont = f.deriveFont(36f);
			bigfont = f.deriveFont(72f);
			logger.debug("10%");
			// instancio un objeto Track y que el se encarge de cargar las imagenes y generar la 
			// matriz con la info del circuito
			// despues al crear la partida dejaria elegir el circuito al jugador que la crea..
			// esto significa que o bien todos los jugadores deben tener el mismo set de circuitos
			// o bien tengo que empaquetar los dos pngs y enviarselos a cada jugador (desde el server?)
			
			// cargo la plantilla del circuito
			track = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("track01_1.png"));
			logger.debug("30%");
			// y cargo el arte del dibujo
			trackart =  ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("trackart01.png"));
			logger.debug("60%");
			// de momento currenttrack sera track1 hasta que implemente la seleccion de circuito
			track1 = new Track(track, trackart,1);
			currenttrack = track1;
			// cargo la splash
			splash =  ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("splash.png"));
			logger.debug("80%");
			// supongo que aqui cargaria todos los sprites
	        character = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("car1_17x18.png"));
	        enemy1img = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("enemy1_17x18.png"));
	        enemy2img = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("enemy2_17x18.png"));
	        enemy3img = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("enemy3_17x18.png"));
	        
	        
	        // luz de freno
	        // estela de aceleracion
	        // etc..
	        // character = getImage(base, "data/character.png");
	        // character2 = getImage(base, "data/character2.png");
	        // character3 = getImage(base, "data/character3.png");
	        // y para la animacion
	        // anim = new Animation();
	        // anim.addFrame(character, 1250);
	        // anim.addFrame(character2, 50);
	        // anim.addFrame(character3, 50);
	        // anim.addFrame(character2, 50);
	        
	        logger.debug("100%");
		} catch (Exception e) {
			logger.error("loadGameResources: error cargando resources ",e);
			System.err.println(e.toString());
			e.printStackTrace();
		}

	}

	
	@Override
	public void start() {
		this.status = ClientStatus.STARTING;
		logger.debug("start: starting...");
		addKeyListener(this);
		car = new Car(character, 30, 260, "car");
		enemy1 = new Car(enemy1img, 60, 260, "enemy1");
		enemy2 = new Car(enemy2img, 90, 260,"enemy2");
		enemy3 = new Car(enemy3img, 110, 260,"enemy3");
		try {
			this.serverIP = InetAddress.getByName(_serverIP);
			this.serverPORT = _serverPORT;
		} catch (UnknownHostException e) {
			logger.error("start: error conectando a {} {}", _serverIP,e);
			System.exit(1);
		}
		Thread thread = new Thread(this);
		thread.start();
		try{
			connectToServer();
		}catch(IOException e){
		    logger.warn("Error, no se ha podido contactar con el servidor {}:{} {}",_serverIP, _serverPORT, e);
		    logger.debug("paso de nuevo a ClientStatus.IDLE");
		    this.status=ClientStatus.IDLE;
		}

	}
	
	protected void connectToServer() throws IOException{
	    logger.debug("starting networkingthread...");
	    RazerClientNetworker rcn = new RazerClientNetworker(this);
	    networkingThread = new Thread(rcn);
	    networkingThread.start();
	    logger.debug("started");

	}

	@Override
	public void stop() {
		super.stop();
	}

	@Override
	public void destroy() {
		this.status = ClientStatus.CLOSING;
		super.destroy();
		
	}

	@Override
	public void run() {
//		logger.debug("run: intentando arrancar networking");
//		startNetworking();
		logger.debug("run: entrando en bucle de juego");
		logger.warn("track1 hardcodeada");
		ConstantFPSEngine engine = new ConstantFPSEngine(true);
		engine.init();
		while (true) {
			engine.reset();
			// FPS
			while(engine.timeToUpdate()){
				// reviso las comunicaciones
				manageNetworkComms();
				// 	actualizo los datos del juego
				gameUpdate(engine.getDeltaTime());	
				engine.updateLoop();
			}// FPS
			
			// y para las animaciones
			animate();
			// repinto todo
			repaint();
			engine.updateFrame();
			this.FPS = engine.getFPS();
			// pauso 17
//			try{
//				Thread.sleep(17);
//			}catch(InterruptedException e){
				
//			}
		} // bucle del juego

	}

	
	private void gameUpdate(long deltaTime){
		// deltaTime es el tiempo que ha pasado
		if(this.status==ClientStatus.PLAYING){
			// primero miro si estoy ralentizado
			if(currenttrack==null) logger.error("ERROR! track1 es null");
			if(currenttrack.getColorAt(jugador.getCenterX(), jugador.getCenterY())==Track.TRACK_GREEN){
				// si es TRACK_GREEN, estoy en la hierba
				// si el jugador no esta ralentizado, lo ralentizo
				if(!jugador.isSnared())	jugador.setSnared(true);
			}else {
				// estoy en la pista
				// si el jugador estaba ralentizado, lo normalizo
				if(jugador.isSnared()) jugador.setSnared(false);
			}
			// en estos updates uno de los coches puede llegar a meta.. !!
			if(this.carnum==1) car.update(deltaTime);
			if(this.carnum==2) enemy1.update(deltaTime);
			if(this.carnum==3) enemy2.update(deltaTime);
			if(this.carnum==4) enemy3.update(deltaTime);
			// reviso si el jugador ha terminado
			if(jugador.isFinished()){
				// terminado!!
				this.status=ClientStatus.CAR_FINISHED;
			}
			// if(car.isAccelerating()){
			// currentSprite = acceleratingSprite
			// o para las animaciones: currentsprite = anim.getImage()
			// }.. y asi con todas las posibilidades
			//

		}else if(this.status==ClientStatus.STARTING){
			this.playername = JOptionPane.showInputDialog("nombre de jugador\n ");
			if(validName(this.playername)){
				// si el nombre de jugador es valido, lo guardo e intento arrancar la red
				this._serverIP = JOptionPane.showInputDialog("servidor de juego (IP)\n");
				this._serverPORT = Integer.parseInt(JOptionPane.showInputDialog("puerto del servidor \n"));
				startNetworking();
			}else{
				// no cambio el estado, asi que volvera a preguntar por el nombre del jugador
			}
		}
		
	}
	
	private boolean validName(String s){
		// un nombre de jugador solo puede tener cifras y letras
		if(s.length()>20) return false;
		Pattern p = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(s);
		boolean b = m.find();
		return !b;
	}
	
	
	/**
	 * este metodo registra el cliente en el servidor 
	 */
	private void startNetworking(){
		// aqui ya deberia tener el socket conectado al servidor
		// o si no estara en estado IDDLE
		// asi que envio el helo (escribo el mensaje en la cola de envio)
		if(this.status!=ClientStatus.IDLE){
			sendHelo();
		// 	y espero la respuesta desde el servidor 
			try{
				processServerResponse(this.readQueue.take());
			}catch(InterruptedException e){
				logger.error("error esperando respuesta al HELO_REQUEST ",e);
			}
		}else{
			// ha fallado la conexion con el servidor, no hago nada y espero a que el gui se lo diga al usuario
			logger.warn("se ha intentado arrancar el networking en estado IDLE, no se ha podido conectar con el servidor");
		}
	}

	protected void manageNetworkComms(){
		boolean blocking = false;
		switch(this.status){
		case WAITING_FOR_PLAYERS:
			// esta llamada cambia a WAITING_FOR_PLAYERS_REQUESTED
			sendWaitingForPlayers();
			break;
		case WAITING_FOR_PLAYERS_REQUESTED:
		case WAITING_FOR_GAME_REQUESTED:
			// pregunto si esta lista la partida
			sendQueryForGame();
			// esta es bloqueante
			blocking = true;
			break;
		case WAITING_FOR_GAME:
			// esta llamada cambia a WAITING_FOR_GAME_REQUESTED
			sendWaitingForGame();
			break;
		case PLAYING:
			// envio la info de mi coche si se ha movido
			// como el servidor solo me contesta, tengo que mandar los updates para que me
			// envie las posiciones actualizadas de los otros jugadores
//			if(jugador.hasmoved()){
				sendCarStatus();
//			}
			break;
		case CLOSING:
			sendClosingMessage();
			break;
		case CAR_FINISHED:
			sendCarFinishedMessage();
			blocking=true;
			break;
		case WAITING_FOR_FINISH:
			sendGameFinishedRequest();
			break;
		case RETRYING_START:
			// reintento el arranque
			logger.debug("reintentando conectar...");
			try{
				connectToServer();
				startNetworking();
			}catch(IOException e){
				logger.warn("Error, no se ha podido contactar con el servidor {}:{}",_serverIP, _serverPORT, e);
			    logger.debug("paso de nuevo a ClientStatus.IDLE");
			    this.status=ClientStatus.IDLE;
			}
			break;
		
		}
		// leo de la cola sin bloquear
		if(blocking){
			try{
	            processServerResponse(this.readQueue.take());
	        }catch(InterruptedException e){
	            logger.error("error esperando respuesta bloqueante ",e);
	        }
		}else{
			RazerProtocolMessage m = this.readQueue.poll();
			if(m!=null) processServerResponse(m);
		}
		// leo de la cola bloqueando
				
	}
	
		
	private ClientStatus processServerResponse(RazerProtocolMessage msg){
		switch(msg.getMessagetype()){
		case HELLO_RESPONSE:
			if(this.status==ClientStatus.WAITING_FOR_HELLOOK){
				this.status=ClientStatus.REGISTERED;
			// 	el id que me ha asignado el servidor
				this.serverid = msg.getIdfromserver();
				logger.debug("processServerResponse: recibido HELLO_RESPONSE, cambiando status a REGISTERED, id de servidor recibido {}",msg.getIdfromserver());
				
				return ClientStatus.REGISTERED;
			}else{
				logger.debug("processServerResponse: recibido HELLO_RESPONSE pero el estado no es CONNECTED por lo que ignoro la respuesta");
				return this.status;
			}
		case NOGAMETOJOIN_RESPONSE:
			// respuesta a unirse a una partida en el caso de que no haya ninguna partida en curso
			logger.debug("processServerResponse: recibido NOGAMETOJOIN, cambiando status a REGISTERED, id de servidor recibido {}", msg.getIdfromserver());
			this.status = ClientStatus.REGISTERED;
			return ClientStatus.REGISTERED;
		case PLAYERADDED_RESPONSE:
			// respuesta de unirse a una partida cuando se ha unido el jugador a una partida
			logger.debug("processServerResponse: recibido PLAYERADDED");
			this.carnum = msg.getNumCar();
			if(this.carnum==1){
				jugador = car;
			}else if(this.carnum==2){
				// cambio car por el enemy1
				jugador = enemy1;
			}else if(this.carnum==3){
				// cambio car por el enemy2
				jugador = enemy2;
			}else if(this.carnum==4){
				// aqui no llega nunca pq el servidor no manda ADDEDEWPLAYER-4 sino GAMEREADY
				// cambio car por el enemy3
				jugador = enemy3;
			}
			// asigno la pista
			// TODO esto habra que cambiarlo cuando haya mas circuitos
			jugador.setTrack(currenttrack);
			return this.status;
		case CREATEGAME_RESPONSE:
			logger.debug("processServerResponse: recibido CREATEGAME_RESPONSE");
			// si yo creo el juego, sere el coche 1
			this.carnum = 1;
			jugador=car;
			// asigno la pista
			// TODO esto habra que cambiarlo cuando haya mas circuitos
			jugador.setTrack(currenttrack);
			break;
		case GAMENOTREADY_RESPONSE:
			logger.debug("processServerResponse: recibido GAMENOTREADY_RESPONSE");
			// 
			this.numberofplayers = msg.getNumberOfPlayers();
			break;
		case GAMEREADY_RESPONSE:
			// ya estamos los 4, se puede empezar el juego
			logger.debug("processServerResponse: recibido GAMEREADY_RESPONSE");
			 if(this.status==ClientStatus.WAITING_FOR_GAME_REQUESTED){
				// si mi carnum es 0 es que soy el 4 jugador
				if((this.carnum==0)||(jugador==null)){
					this.carnum=4;
					jugador = enemy3;
					jugador.setTrack(currenttrack);
				}
				// relleno los nombres de los jugadores
				this.players[0]=msg.getPlayers()[0];
				this.players[1]=msg.getPlayers()[1];
				this.players[2]=msg.getPlayers()[2];
				this.players[3]=msg.getPlayers()[3];

			}
			 this.status=ClientStatus.GAME_READY;
			return ClientStatus.GAME_READY;
		case NOOP_RESPONSE:
			logger.debug("processServerResponse: recibido NOOP_RESPONSE");
			// si recibo un noop, no hago nada.. normalmente me contestaran noop al perguntar
			// si una partida esta lista y no lo este aun
			break;
		case CARUPDATE_RESPONSE:
			logger.debug("processServerResponse: recibido CARUPDATE_RESPONSE");
			// en msg tengo los parametros nuevos de los coches
			if(this.status==ClientStatus.PLAYING){
				// en this.carnum tengo mi num de coche
				// coche1
				if(this.carnum!=1) 
					updateCar(car, msg.getCar1update());
				// coche2
				if(this.carnum!=2) 
					updateCar(enemy1,msg.getCar2update());
				// coche3
				if(this.carnum!=3) 
					updateCar(enemy2,msg.getCar3update());
				// coche4
				if(this.carnum!=4) 
					updateCar(enemy3, msg.getCar4update());
				if(jugador.getLapnum()==this.lapnumber){
					// juego terminado
					logger.debug("processServerResponse: juego terminado, ganador {}",jugador.getName());
					return ClientStatus.GAME_FINISHED;
				}else{
					return ClientStatus.PLAYING;
				}
			}else{
				logger.warn("processServerResponse: ... pero el estado no es PLAYING!");
			}
			break;
		case CARFINISHED_RESPONSE:
			logger.info("processServerResponse: recibido CARFINISHED_RESPONSE");
//			System.out.println("recibido carfinished_response");
			// estoy esperando qeu el servidor me conteste con el resultado final de la carrera
			// en msg tengo la lista con los puestos 1,2,3,4
			this.tmpresults = msg.getResults();
			// camnbio el estado a showresults
			this.status=ClientStatus.SHOWRESULTS;
			return ClientStatus.SHOWRESULTS;
			
		}
		return null;
	}
	
		
private void updateCar(Car car, CarInfo data){
	
	car.setCenterX(data.getCenterx());
	car.setCenterY(data.getCentery());
	car.setOrientation(data.getRotation());
	car.setRotatingleft(data.isRotatingleft());
	car.setRotatingright(data.isRotatingright());
	car.setAccelerating(data.isAccelerating());
	car.setBraking(data.isBraking());
	car.setSnared(data.isSnared());
}


	
	/**
	 * este metodo envia el mensaje inicial solicitando el registro del cliente en el servidor
	 * es un mensaje bloqueante por lo que el cliente se queda esperando la respuesta
	 */
	protected void sendHelo(){
		// me registro en el servidor
		logger.debug("sendHelo: enviando HELLO_REQUEST");
		long date = System.currentTimeMillis();
		sendQueue.add(RazerProtocolBinarySocket.createHelloRequest(date, idcliente, this.playername));
		// cambio el estado
		this.status = ClientStatus.WAITING_FOR_HELLOOK;
		logger.debug("sendHelo: cambiando status a WAITING_FOR_HELLOOK");
	}
	
	private void sendGameFinishedRequest(){
		logger.debug("sendGameFinishedRequest: enviando GAMEFINISHED_REQUEST");
	    sendQueue.add(RazerProtocolBinarySocket.createGameFinishedRequest(serverid));
	}
	
	/**
	 * este metodo envia una peticion para unirse a alguna partida existente cualquiera
	 * es un mensaje bloqueante por lo que el cliente se queda esperando respuesta
	 */
	protected void sendWaitingForGame(){
		logger.debug("sendWaitingForGame: enviando JOINGAME_REQUEST");
	    sendQueue.add(RazerProtocolBinarySocket.createJoinRequest(serverid));
		logger.debug("sendWaitingForGame: cambiando status a WAITING_FOR_GAME_REQUESTED");
		this.status=ClientStatus.WAITING_FOR_GAME_REQUESTED;
	}
	
	/**
	 * este metodo envia una peticion para crear una partida nueva
	 * y rellenarla con jugadores
	 * es un mensaje bloqueante, el cliente se queda esperando respuesta
	 */
	protected void sendWaitingForPlayers(){
		logger.debug("sendWaitingForPlayers: enviando CREATEGAME_REQUEST");
	    sendQueue.add(RazerProtocolBinarySocket.createCreateGameRequest(serverid));
		logger.debug("sendWaitingForPlayers: cambiando status a WAITING_FOR_PLAYERS_REQUESTED");
		this.status=ClientStatus.WAITING_FOR_PLAYERS_REQUESTED;
	}
	
	/**
	 * este metodo envia una consulta sobre el estado de una partida
	 * si esta lista para empezar o no
	 */
	private void sendQueryForGame(){
		logger.debug("sendQueryForGame: enviando GAMEREADY_REQUEST");
		sendQueue.add(RazerProtocolBinarySocket.createGameReadyRequest(serverid));
	}
	
	private void sendCarStatus(){
		logger.debug("sendCarStatus: enviando CARUPDATE_REQUEST");
		// este puede llenar la cola
		// si ocurre, descarto el mensaje mas antiguo
		if(sendQueue.remainingCapacity()==0){
		    sendQueue.remove();
		}
	    sendQueue.add(RazerProtocolBinarySocket.createCarUpdateRequest(jugador, this.carnum, serverid));
	}
	
	private void sendClosingMessage(){
		logger.debug("sendClosingMessage: enviando CLOSING");
	    sendQueue.add(RazerProtocolBinarySocket.createClosingRequest(serverid));
	}
	
	private void sendCarFinishedMessage(){
		logger.debug("sendCarFinishedMessage: enviando CARFINISHED_REQUEST");
	    sendQueue.add(RazerProtocolBinarySocket.createCarFinishedRequest(serverid));
		this.status=ClientStatus.WAITING_FOR_FINISH;
	}
	
	// en el metodo animate meto todas las animaciones que he cargado al inicio
	private void animate() {
		// anim.update(10);
	}

	@Override
	public void update(Graphics g) {
		if (image == null) {
			image = createImage(this.getWidth(), this.getHeight());
			second = image.getGraphics();
		}

		second.setColor(getBackground());
		second.fillRect(0, 0, getWidth(), getHeight());
		second.setColor(getForeground());
		paint(second);
		g.drawImage(image, 0, 0, this);
	}

	@Override
	public void paint(Graphics g) {
//		System.out.println("EN PAINT: "+this.status);
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(
		        RenderingHints.KEY_ANTIALIASING,
		        RenderingHints.VALUE_ANTIALIAS_ON);

		g2d.setColor(getBackground());
		g2d.fillRect(0,0,getWidth(),getHeight());
		if(this.status!=ClientStatus.PLAYING){
			// solo pinto el fondo mientras no este jugando
			g2d.drawImage(splash, 0,0,this);
		}
		g2d.setColor(Color.WHITE);
		g2d.setFont(normalfont);
		g2d.drawString("fps: "+this.FPS, 50, 50);		
		// y ahora los estados
		if(this.status==ClientStatus.STARTING){
			// inicio del juego, pido el nombre al usuario
			 
		}else if(this.status==ClientStatus.WAITING_FOR_GAME_REQUESTED){
			g2d.drawString("uniendose a partida...",200,100);
			g2d.drawString(this.numberofplayers+"/4 jugadores", 200, 200);
		}else if(this.status==ClientStatus.WAITING_FOR_PLAYERS_REQUESTED){
			g2d.drawString("creando partida...",200,100);
			g2d.drawString(this.numberofplayers+"/4 jugadores", 200, 200);
		}else if(this.status==ClientStatus.REGISTERED){
			g2d.drawString("'C' - crear partida nueva", 200,50);
			g2d.drawString("'J' - unirse a partida", 200,100);
		}else if((this.status==ClientStatus.WAITING_FOR_GAME)){
//				||(this.status==ClientStatus.WAITING_FOR_GAME_REQUESTED)){
			g2d.setColor(Color.WHITE);
			g2d.drawString("'C' - crear partida nueva", 200,50);
			g2d.setColor(Color.YELLOW);
			g2d.drawString("'J' - unirse a partida", 200,100);
		}else if((this.status==ClientStatus.WAITING_FOR_PLAYERS)){
//				||(this.status==ClientStatus.WAITING_FOR_PLAYERS_REQUESTED)){
			g2d.setColor(Color.YELLOW);
			g2d.drawString("'C' - crear partida nueva", 200,50);
			g2d.setColor(Color.WHITE);
			g2d.drawString("'J' - unirse a partida", 200,100);
		}else if(this.status==ClientStatus.GAME_READY){ // 985748
			g2d.setColor(Color.WHITE);
			g2d.drawString("corredor 1: "+this.players[0], 200, 100);
			g2d.drawString("corredor 2: "+this.players[1], 200, 150);
			g2d.drawString("corredor 3: "+this.players[2], 200, 200);
			g2d.drawString("corredor 4: "+this.players[3], 200, 250);
			g2d.drawString("'Y' - EMPEZAR PARTIDA!", 300, 400);
			
		}else if(this.status==ClientStatus.ONYOURMARKS){
			g2d.drawImage(currenttrack.getTrackart(), 0, 0, this);
			g2d.drawString("fps: "+this.FPS, 50, 50);		
			car.paint(g2d, this);
			enemy1.paint(g2d, this);
			enemy2.paint(g2d, this);
			enemy3.paint(g2d, this);
			// cuenta atras
			g2d.setColor(Color.MAGENTA);
			g2d.setFont(bigfont);
			if(countdownindex==0){
				g2d.drawString("...", 300, 250);
				accframes++;
				if(accframes==30){
					countdownindex=1;
					accframes=0;
				}
			}else if(countdownindex==1){
				g2d.drawString("TRES!", 300, 250);
				accframes++;
				if(accframes==30){
					countdownindex=2;
					accframes=0;
				}
			}else if(countdownindex==2){
				g2d.drawString("DOS!", 300, 250);
				accframes++;
				if(accframes==30){
					countdownindex=3;
					accframes=0;
				}
			}else if(countdownindex==3){
				accframes++;
				g2d.drawString("UNO!!!", 300, 250);
				if(accframes==30){
					this.status=ClientStatus.PLAYING;
					countdownindex=0;
				}
			}
		}else if((this.status==ClientStatus.PLAYING)){
			g2d.drawImage(currenttrack.getTrackart(), 0, 0, this);
			// y pinto la vuelta
			g2d.drawString(String.valueOf(jugador.getLapnum())+"/"+String.valueOf(this.lapnumber), 700, 100);
			// y los FPS ***************************************
			g2d.drawString("fps: "+this.FPS, 50, 50);		
			// y el siguiente milestone
			g2d.drawPolygon(jugador.getTrack().getNextMilestone().getForma());
			// **************************************************
			car.paint(g2d, this);
			if(car.isDrifting()){
				// pinto el rastro del coche de negro
				int [][] ruedas = car.getWheelCoords();
				try{
					pintapunto(ruedas[0][0], ruedas[0][1], currenttrack);
					pintapunto(ruedas[1][0], ruedas[1][1], currenttrack);
					pintapunto(ruedas[2][0], ruedas[2][1], currenttrack);
					pintapunto(ruedas[3][0], ruedas[3][1], currenttrack);
				}catch(Exception e){
					// ignoro cualquier error
				}
			}
			enemy1.paint(g2d, this);
			if(enemy1.isDrifting()){
				// pinto las ruedas del coche de negro
				int [][] ruedas = enemy1.getWheelCoords();
				try{
					pintapunto(ruedas[0][0], ruedas[0][1], currenttrack);
					pintapunto(ruedas[1][0], ruedas[1][1], currenttrack);
					pintapunto(ruedas[2][0], ruedas[2][1], currenttrack);
					pintapunto(ruedas[3][0], ruedas[3][1], currenttrack);
				}catch(Exception e){
					// ignoro cualquier error
				}
			}

			enemy2.paint(g2d, this);
			if(enemy2.isDrifting()){
				// pinto las ruedas del coche de negro
				int [][] ruedas = enemy2.getWheelCoords();
				try{
					pintapunto(ruedas[0][0], ruedas[0][1], currenttrack);
					pintapunto(ruedas[1][0], ruedas[1][1], currenttrack);
					pintapunto(ruedas[2][0], ruedas[2][1], currenttrack);
					pintapunto(ruedas[3][0], ruedas[3][1], currenttrack);
				}catch(Exception e){
					// ignoro cualquier error
				}
			}

			enemy3.paint(g2d, this);
			if(enemy3.isDrifting()){
				// pinto las ruedas del coche de negro
				int [][] ruedas = enemy3.getWheelCoords();
				try{
					pintapunto(ruedas[0][0], ruedas[0][1], currenttrack);
					pintapunto(ruedas[1][0], ruedas[1][1], currenttrack);
					pintapunto(ruedas[2][0], ruedas[2][1], currenttrack);
					pintapunto(ruedas[3][0], ruedas[3][1], currenttrack);
				}catch(Exception e){
					// ignoro cualquier error
				}
			}
			// y aqui pinto el velocimetro
			pintavelocimetro(g2d);
			
		}else if(this.status==ClientStatus.GAME_FINISHED){
			g2d.drawImage(currenttrack.getTrackart(), 0, 0, this);
			// y pinto la vuelta
			g2d.drawString(String.valueOf(jugador.getLapnum())+"/"+String.valueOf(this.lapnumber), 700, 50);
			g2d.drawString("FINISH!", 300, 200);
		}else if(this.status==ClientStatus.SHOWRESULTS){
			g2d.drawString("1 - "+this.tmpresults[0], 200, 100);
			g2d.drawString("2 - "+this.tmpresults[1], 200, 150);
			g2d.drawString("3 - "+this.tmpresults[2], 200, 200);
			g2d.drawString("4 - "+this.tmpresults[3], 200, 250);
			g2d.drawString("'K' - continuar", 200, 400);
		}else if(this.status==ClientStatus.WAITING_FOR_FINISH){
			g2d.drawString("META!!", 300, 100);
			g2d.drawString("esperando al resto de corredores... ",150,150);
		}else if(this.status==ClientStatus.IDLE){
		    g2d.drawString("No se ha podido conectar con "+this._serverIP+":"+this._serverPORT, 100, 200);
		    g2d.drawString("'R' - reintentar",250,250);
		}else if(this.status==ClientStatus.RETRYING_START){
			g2d.drawString("Reintentando conectar con "+this._serverIP+":"+this._serverPORT, 100, 200);
		}
		
//		g2d.setColor(Color.RED);
//		g2d.drawString(this.status.toString(),10, 450);
//		String carnum = String.valueOf(this.carnum);
//		String playername = jugador!=null?jugador.getName():"null";
//		g2d.drawString(this.playername+": soy el coche "+carnum+" jugador="+playername, 10, 400);
		
		
	}
	
	private void pintapunto(int x, int y, Track t){
		//      X
		//     XOX
		//      X
		if(t.getTrackInfo()[x][y-1]!=Track.TRACK_GREEN){
			t.getTrackart().setRGB(x, y-1, t.getInTrackDriftColor());
		}else{
			// lo pinto marron
			t.getTrackart().setRGB(x, y-1, t.getOutTrackDriftColor());
		}
		if(t.getTrackInfo()[x-1][y]!=Track.TRACK_GREEN){
			t.getTrackart().setRGB(x-1, y, t.getInTrackDriftColor());
		}else{
			t.getTrackart().setRGB(x-1, y, t.getOutTrackDriftColor());
		}
		if(t.getTrackInfo()[x][y]!=Track.TRACK_GREEN){
			t.getTrackart().setRGB(x, y, t.getInTrackDriftColor());
		}else{
			t.getTrackart().setRGB(x, y, t.getOutTrackDriftColor());
		}
		if(t.getTrackInfo()[x+1][y]!=Track.TRACK_GREEN){
			t.getTrackart().setRGB(x+1, y, t.getInTrackDriftColor());
		}else{
			t.getTrackart().setRGB(x+1, y, t.getOutTrackDriftColor());
		}
		if(t.getTrackInfo()[x][y+1]!=Track.TRACK_GREEN){
			t.getTrackart().setRGB(x, y+1, t.getInTrackDriftColor());
		}else{
			t.getTrackart().setRGB(x, y+1, t.getOutTrackDriftColor());
		}
		
	}
	
	private void pintavelocimetro(Graphics2D g2d){
		g2d.drawArc(20, 300, 200, 200, 0, 180);
		g2d.drawArc(30,300,180, 180, 0, 180);
//		jugador.getCurrentspeed();
//		g2d.drawLine(120, 300, x2, y2)

	}

	@Override
	public void keyPressed(KeyEvent e) {
		// al pulsar las teclas cambio el estado
		// asi puedo escuchar varias teclas a la vez
		switch (e.getKeyCode()) {
		// acelerar
		case KeyEvent.VK_W:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setAccelerating(true);
			break;
		// frenar
		case KeyEvent.VK_S:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setBraking(true);
			break;
		// giro izq
		case KeyEvent.VK_A:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setRotatingleft(true);
			break;
		// giro dcha
		case KeyEvent.VK_D:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setRotatingright(true);
			
		
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		// al liberar las teclas paro los movimientos
		switch (e.getKeyCode()) {
		// arriba
		case KeyEvent.VK_W:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setAccelerating(false);
			break;
		// abajo
		case KeyEvent.VK_S:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setBraking(false);
			break;
		// izq
		case KeyEvent.VK_A:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setRotatingleft(false);
			break;
		// dcha
		case KeyEvent.VK_D:
			// solamente si la partida esta en curso
			if(this.status==ClientStatus.PLAYING) jugador.setRotatingright(false);
			// y luego varios disparos para usar las mejoras
			break;
		case KeyEvent.VK_J:
			if(this.status==ClientStatus.REGISTERED){
				logger.debug("keyreleased: 'J' cambiando estado a WAITING_FOR_GAME");
				this.status = ClientStatus.WAITING_FOR_GAME;
			}
			break;
		case KeyEvent.VK_C:
			if(this.status==ClientStatus.REGISTERED){
				logger.debug("keyreleased: 'C' cambiando estado a WAITING_FOR_PLAYERS");
				this.status = ClientStatus.WAITING_FOR_PLAYERS;
			}
			break;
		case KeyEvent.VK_K:
			if(this.status==ClientStatus.SHOWRESULTS){
				this.status = ClientStatus.REGISTERED;
				this.jugador.setLapnum(1);				
			}
			break;
		case KeyEvent.VK_Y:
			if(this.status==ClientStatus.GAME_READY) this.status = ClientStatus.ONYOURMARKS;
			break;
		case KeyEvent.VK_R:
		    if(this.status==ClientStatus.IDLE){
		    	logger.debug("pulsada R paso a ClientStatus.RETRYING_START");
		       this.status=ClientStatus.RETRYING_START;
		    }
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}


	   public InetAddress getServerIP() {
        return serverIP;
    }

    public int getServerPORT() {
        return serverPORT;
    }

    public BlockingQueue<RazerProtocolMessage> getSendQueue() {
	        return sendQueue;
	    }

    public BlockingQueue<RazerProtocolMessage> getReadQueue() {
        return readQueue;
    }

	public void setServerIP(InetAddress serverIP) {
		this.serverIP = serverIP;
	}

	public void setServerPORT(int serverPORT) {
		this.serverPORT = serverPORT;
	}
}
